"""
    Turbogears widgets inheritors. Customization for mnemoply.
"""

import re
from datetime import datetime

import dispatch
from sqlobject import col, classregistry, declarative, SQLObject

import turbogears
import cherrypy
import formencode
import cgi
from formencode.schema import Schema
from turbogears import widgets, validators
from turbogears.database import so_columns

from mnemoply import model
from mnemoply.model.columns import SOGenderCol, Gender, GenderValidator, TariffValidator, SOTariffCol,PhoneValidator,SOPhoneCol,PhoneCol
from mnemoply.utils import Wrapper, planSortFunction, PhoneFormat
from mnemoply.common_knowledge import actual_years, day_captions, month_captions
from mnemoply.menu import menu

class EmptyStringConverter(validators.FancyValidator):
    """
    Converts empty string to None value. Doesn't change any other values.
    """
    def _to_python(self, value, state):
        if isinstance(value, basestring) and len(value) == 0:
            return None
        else:
            return value

def option_selected(input_value, option_value):
    if hasattr(input_value, '__getitem__') and \
            not isinstance(input_value, basestring):
        return option_value in [row.id for row in input_value]
    elif isinstance(input_value, SQLObject):
        return option_value == input_value.id
    else:
        return option_value == input_value

class DataSelectField(widgets.SingleSelectField):
    validator = EmptyStringConverter()

    def selected(self, input_value, option_value):
        return self._is_option_selected(option_value, input_value)
    def _is_option_selected(self, option_value, value):
        if option_selected(value, option_value):
            return "selected"

# Accepts {int_key: title} dict
class DictField(DataSelectField):
    """
        Drop-down select widget.
    """

    params = ['data']

    data = {}

    def __init__(self, not_empty=False, *args, **kw):
        super(DictField, self).__init__(*args, **kw)

        options = []
        if not not_empty:
            options.append((None, ''))
        options.extend(self.data.items())
        self.options = options

        self.validator = validators.Int(not_empty=not_empty)

class YearField(DictField):
    """
        Drop-down menu with actual years as content.
    """

    data = dict([(x, x) for x in actual_years])

class DayOfWeekField(DictField):
    """
        Drop-down menu with days of week.
    """

    data = dict([(number + 1, title) for number, title in enumerate(day_captions[1:])])

class HourMinuteValidator(validators.FancyValidator):
    """
        Validator for hh:mm input data.
    """

    messages = {
        'h:m': "Please enter hour:minute pair",
    }

    def _to_python(self, value, state):
        try:
            assert isinstance(value, (str, unicode))

            (hour, minute) = [int(x) for x in value.split(':')]
            return hour, minute
        except (AssertionError, ValueError, TypeError):
            raise formencode.Invalid(self.message('h:m', state), value, state)

    def _from_python(self, value, state):
        assert isinstance(value, list)
        (hour, minute) = value
        return '%02d:%02d' % (hour, minute)

class HourMinuteField(widgets.TextField):
    """
        Text field with HourMinuteValidator.
    """

    def __init__(self, not_empty=False, *args, **kw):
        super(HourMinuteField, self).__init__(*args, **kw)

        self.validator = HourMinuteValidator(not_empty=not_empty)

class TimeValidator(validators.FancyValidator):
    """
        Validator for hh:mm input data.
        Returns datetime.datetime(), not (hour, minute) pair like HourMinuteValidator.
    """

    messages = {
        'h:m': "Please enter hour:minute pair",
    }

    def _to_python(self, value, state):
        try:
            assert isinstance(value, (str, unicode))
            (hour, minute) = [int(x) for x in value.split(':')]

            return datetime(2000, 1, 1, hour, minute)
        except (AssertionError, ValueError, TypeError):
            raise formencode.Invalid(self.message('h:m', state), value, state)

    def _from_python(self, value, state):
        return '%02d:%02d' % (value.hour, value.minute)

class TimeField(widgets.TextField):
    """
        Text field with TimeValidator.
    """

    def __init__(self, not_empty=False, *args, **kw):
        super(TimeField, self).__init__(*args, **kw)
        self.validator = TimeValidator(not_empty=not_empty)

class MonthField(DictField):
    """
        Drop-down select widget with month as content.
    """

    data = dict([(number + 1, title) for number, title in enumerate(month_captions[1:])])

# Discont
class DiscontFieldValidator(validators.FancyValidator):
    """
        Discont validator.
        Discont is string with 11 columns as separator with 12 numbers as data.
    """

    def _to_python(self, value, state):
        try:
            if value is None:
                return ':::::::::::'

            assert isinstance(value, dict)

            vals = []
            for d in range(1, 13):
                part = value[str(d)]
                vals.append((part is not None) and str(part) or '')

            return ':'.join(vals)
        except (AssertionError, ValueError, TypeError):
            raise formencode.Invalid('Internal error in DiscontFieldValidator', value, state)

    def _from_python(self, value, state):
        if value is None:
            return dict([(str(x), None) for x in range(1, 13)])

        value = value.split(':')
        assert len(value) == 12

        result = {}
        for number, x in enumerate(value):
            result[str(number + 1)] = (x != '') and x or None
        return result

class DiscontSchema(Schema):
    """
        Validation schema which intakes DiscontValidator and DiscontFieldValidator.
    """

    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [DiscontFieldValidator(), model.DiscontValidator()]

class DiscontField(widgets.CompoundFormField):
    """
        Text field with DiscontSchema validation.
    """

    template = """
        <table border="0" cellspacing="0" cellpadding="2" xmlns:py="http://purl.org/kid/ns#">
            <tr py:for="i, field in enumerate(fields)"
                class="${i%2 and 'odd' or 'even'}"
            >
                <th>
                    <label class="fieldlabel" for="${field.field_id}" py:content="field.label" />
                </th>
                <td>
                    <span py:replace="field.display(value_for(field), **params_for(field))" />
                    <span py:if="error_for(field)" class="fielderror" py:content="error_for(field)" />
                    <span py:if="field.help_text" class="fieldhelp" py:content="field.help_text" />
                </td>
            </tr>
        </table>
    """

    def __init__(self, not_empty=False, *args, **kw):
        field_validator = formencode.validators.Number(not_empty=not_empty)

        fields = [
            DictionaryField(
                name=str(x),
                model_object_name='Discont',
                validator=field_validator,
                label=month_captions[x])
            for x in range(1, 13)
            ]

        kw['validator'] = DiscontSchema()

        super(DiscontField, self).__init__(fields=fields, *args, **kw)

    def adjust_value(self, value, **kw):
        return self.validator.from_python(value)

class TariffFieldValidator(validators.FancyValidator):
    """
        Tariff validator.
        Tariff is string with 11 columns as separator with 12 numbers as data.
    """

    def _to_python(self, value, state):
        try:
            if value is None:
                return ':::::::::::'

            assert isinstance(value, dict)

            vals = []
            for d in range(1, 13):
                part = value[str(d)]
                vals.append((part is not None) and str(part) or '')

            return ':'.join(vals)
        except (AssertionError, ValueError, TypeError):
            raise formencode.Invalid('Internal error in TariffFieldValidator', value, state)

    def _from_python(self, value, state):
        if value is None:
            return dict([(str(x), None) for x in range(1, 13)])

        value = value.split(':')
        assert len(value) == 12

        result = {}
        for number, x in enumerate(value):
            result[str(number + 1)] = (x != '') and x or None
        return result

class TariffSchema(Schema):
    """
        Validation schema which intakes TariffValidator and TariffFieldValidator.
    """

    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [TariffFieldValidator(), TariffValidator()]

class TariffField(widgets.CompoundFormField):
    """
        Text field with TariffSchema validation.
    """

    template = """
        <table border="0" cellspacing="0" cellpadding="2" xmlns:py="http://purl.org/kid/ns#">
            <tr py:for="i, field in enumerate(fields)"
                class="${i%2 and 'odd' or 'even'}"
            >
                <th>
                    <label class="fieldlabel" for="${field.field_id}" py:content="field.label" />
                </th>
                <td>
                    <span py:replace="field.display(value_for(field), **params_for(field))" />
                    <span py:if="error_for(field)" class="fielderror" py:content="error_for(field)" />
                    <span py:if="field.help_text" class="fieldhelp" py:content="field.help_text" />
                </td>
            </tr>
        </table>
    """

    def __init__(self, not_empty=False, *args, **kw):
        field_validator = formencode.validators.Number(not_empty=not_empty)

        fields = [
            widgets.TextField(str(x), validator=field_validator, label=month_captions[x])
                for x in range(1, 13)
        ]

        kw['validator'] = TariffSchema()

        super(TariffField, self).__init__(fields=fields, *args, **kw)

    def adjust_value(self, value, **kw):
        return self.validator.from_python(value)

class Span(widgets.CompoundFormField):
    """
        Simple span html tag.
    """

    template = "<span>${fields[0].label}</span>"

    def __init__(self, value, *args, **kw):
        fields = [widgets.TextField(str(), label=value)]
        super(Span, self).__init__(fields=fields, *args, **kw)

class GridField(widgets.CompoundFormField):
    """
        Table widget.
    """

    #template = """
        #<table border="0" cellspacing="0" cellpadding="2" xmlns:py="http://purl.org/kid/ns#">
            #<tr py:for="i, field in enumerate(fields)"
                #class="${i%2 and 'odd' or 'even'}"
            #>
                #<th>
                    #<label class="fieldlabel" for="${field.field_id}" py:content="field.label" />
                #</th>
                #<td>
                    #<span py:replace="field.display(value_for(field), **params_for(field))" />
                    #<span py:if="error_for(field)" class="fielderror" py:content="error_for(field)" />
                    #<span py:if="field.help_text" class="fieldhelp" py:content="field.help_text" />
                #</td>
            #</tr>
        #</table>
    #"""

    # Declare or child (member) widgets
    member_widgets = ["widget"]

    template = """
        <table id="${widget.name}" class="grid" cellpadding="0" cellspacing="1" border="0" xmlns:py="http://purl.org/kid/ns#">
            <tr py:if="widget.headers">
                <td py:for="head in widget.headers">
                    ${head}
                </td>
            </tr>
            <tr py:for="i, row in enumerate(widget.rows)" class="${i%2 and 'odd' or 'even'}">
                <td py:for="cell in row">
                    ${cell.display()}
                </td>
            </tr>
        </table>
    """

    def __init__(self, widget, *args, **kw):
        super(GridField, self).__init__(*args, **kw)

        self.widget = widget
        kw['validator'] = None

class CookieTabber(widgets.Tabber):
    """
        Tab widget. You can see instanses of this class in MassActions.
    """

    def __init__(self, *args, **kw):
        super(CookieTabber, self).__init__(use_cookie=True, *args, **kw)

class FieldGroup(declarative.Declarative):
    pass

class DecimalValidator(formencode.validators.Number):
    """
        Decimal validator for russian reality.
    """

    def _to_python(self, value, state):
        if value.count(","):
            value = value.replace(",", ".")
        try:
            value = float(value)
            if value == int(value):
                return int(value)
            return value
        except ValueError:
            from formencode.api import Invalid
            raise Invalid(self.message('number', state),
                          value, state)



class EmptyValidator(validators.FancyValidator):
    if_missing = None
    if_empty = ''

def name2label(name):
    """
    Convert a column name to a Human Readable name.
    """
    # Create label from the name:
    #   1) Convert _ to spaces
    #   2) Convert CamelCase to Camel Case
    #   3) Upcase first character of Each Word
    # Note: I *think* it would be thread-safe to
    #       memoize this thing.
    return ' '.join([s.capitalize() for s in
               re.findall(r'([A-Z][a-z0-9]+|[a-z0-9]+|[A-Z0-9]+)', name)])

class DictionaryField(DataSelectField):
    """
        Drop-down select widget. Takes data from model.
    """

    params = ['model_object_name', 'records', 'empty_caption', 'caption_func', 'sort_func']

    model_object_name = None
    records = None
    empty_caption = ''
    caption_func = [None]
    sort_func = [None]

    def __init__(self, not_empty=False, *args, **kw):
        DataSelectField.__init__(self, *args, **kw)

        assert self.model_object_name is not None
        self.model_object = classregistry.findClass(self.model_object_name)

        if self.records is None:
            self.records = self.model_object.select()
        if getattr(self, 'label', None) is None:
            self.label = self.model_object.model_title
        self.validator = validators.Int(not_empty=not_empty)

    def get_caption(self, obj):
        if self.caption_func[0]:
            return self.caption_func[0](obj)
        else:
            return obj.title

    def sort_records(self, records):
        if self.sort_func[0]:
            self.sort_func[0](records)
        else:
            records.sort(key=lambda x: x.title)

    def get_options(self, records):
        records = list(records)
        self.sort_records(records)
        captions = [(item.id, self.get_caption(item)) for item in records]
 
        result = []
        if not self.validator.not_empty:
            result += [('', self.empty_caption)]
        result += captions
        return result

    def display(self, *args, **kw):
        records = kw.get('records', self.records)
        if callable(records):
            records = records()
        kw['options'] = self.get_options(records)
        return super(DictionaryField, self).display(*args, **kw)

class AutoCompleteFieldValidator(validators.FancyValidator):
    """
        Validator for AutoCompleteField.
    """

    def __init__(self, model_object, not_empty=False, *args, **kw):
        kw['model_object'] = model_object
        super(validators.FancyValidator, self).__init__(*args, **kw)

    def _to_python(self, value, state):
        try:
            if value is None:
                return None

            assert isinstance(value, dict)

            return value["hidden"]
        except (AssertionError, ValueError, TypeError):
            raise formencode.Invalid('Internal error in AutoCompleteFieldValidator', value, state)

    def _from_python(self, value, state):
        if value is None:
            return dict(hidden='')
        else:
            return dict(hidden=str(value), text=self.model_object.get(value).title)

def createPlanField(label):
    """
        Get new instance of DictionaryField with plan model.
    """

    return DictionaryField(model_object_name='Plan',
        sort_func=[planSortFunction],
        label=label)

def createGroupField(year):
    """
        Create new instance of DictionaryField with group model.
    """

    return DictionaryField(
        name="group_id",
        model_object_name='Group',
        not_empty=True,
        caption_func=[lambda x: '(%d) %s' % (x.maxStudents - x.cacheStudents, x.title)],
        sort_func=[lambda x: x.sort(key=lambda y: y.cacheTitle)],
        records=[g for g in model.Group.select() if g.initialPlan.year == year],  # TODO
        label=_("In group"))

def createProjectDefenceField(year):
    """
        Create new instance of DictionaryField with ProjectDefence model.
    """

    return DictionaryField(
        name="defence_id",
        model_object_name='ProjectDefence',
        not_empty=True,
        label=_("Project defence"),
        caption_func=[lambda x: '%s' % x.title],
        sort_func=[lambda x: x.sort(key=lambda y: y.title)],
        records=[g for g in model.ProjectDefence.select() if g.group.initialPlan.year == year],  # TODO
    )

class MyAutoCompleteField(widgets.AutoCompleteField):
    """
        Base class for autocomplete field.
    """

    # Unsupported param, provided here to remove warnings
    params = ["not_empty"]
    not_empty = True

    def adjust_value(self, value, **kw):
        return self.validator.from_python(value)

class TopicFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.Topic), validators.Int()]

class TopicField(MyAutoCompleteField):
    """
        Autocomplete field for Topic model object.
    """

    search_controller = "/Search/topic"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = TopicFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class GroupFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.Group), validators.Int()]

class GroupField(MyAutoCompleteField):
    """
        Autocomplete field for Group model object.
    """

    search_controller = "/Search/group"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = GroupFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class ProjectDefenceFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.ProjectDefence), validators.Int()]

class ProjectDefenceField(MyAutoCompleteField):
    """
        Autocomplete field for ProjectDefence model object.
    """

    search_controller = "/Search/project_defence"

    def __init__(self, year=0, not_empty=False, *args, **kw):
        kw['validator'] = ProjectDefenceFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        if year:
            self.search_controller = "%s/%s" % (self.search_controller, year)
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class PlanTopicFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.Topic), validators.Int()]

class PlanTopicField(MyAutoCompleteField):
    """
        Autocomplete field for PlanTopic model object.
    """

    search_controller = "/Search/plan_topic"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = PlanTopicFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class FinalTopicFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.FinalWorkTopic), validators.Int()]

class FinalTopicField(MyAutoCompleteField):
    """
        Autocomplete field for FinalTopic model object.
    """

    search_controller = "/Search/final_topic"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = FinalTopicFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class CustomerFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.Customer), validators.Int()]

class CustomerField(MyAutoCompleteField):
    """
        Autocomplete field for Customer model object.
    """

    search_controller = "/Search/customer"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = CustomerFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())

        super(MyAutoCompleteField, self).__init__(*args, **kw)

class PersonFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.Person), validators.Int()]

class PersonField(MyAutoCompleteField):
    """
        Autocomplete field for Person model object.
    """

    search_controller = "/Search/person"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = PersonFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class DiscontCardFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.DiscontCard), validators.Int()]

class DiscontCardField(MyAutoCompleteField):
    """
        Autocomplete field for DiscontCard model object.
    """

    search_controller = "/Search/card"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = DiscontCardFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class StudentFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.Student), validators.Int()]

class StudentField(MyAutoCompleteField):
    """
        Autocomplete field for Student model object.
    """

    search_controller = "/Search/student"
    complete_delay = 0.900

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = StudentFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class AgreementFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.StudentAgreement), validators.Int()]

class AgreementField(MyAutoCompleteField):
    """
        Autocomplete field for StudentAgreement model object.
    """

    search_controller = "/Search/agreement"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = AgreementFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class StudentInGroupFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.StudentInGroup), validators.Int()]

class StudentInGroupField(MyAutoCompleteField):
    """
        Autocomplete field for StudentInGroup model object.
    """

    search_controller = "/Search/student_in_group"
    complete_delay = 0.900

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = StudentInGroupFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class TemporaryPaymentFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.TemporaryPaymentRecord), validators.Int()]

class TemporaryPaymentField(MyAutoCompleteField):
    """
        Autocomplete field for TemporaryPaymentRecord model object.
    """

    search_controller = "/Search/temp_payment"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = TemporaryPaymentFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class CouponFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.Coupon), validators.Int()]

class CouponField(MyAutoCompleteField):
    """
        Autocomplete field for Coupon model object.
    """

    search_controller = "/Search/coupon"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = CouponFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)


class CouponActiveFieldSchema(Schema):
    allow_extra_fields = False
    if_key_missing = None

    chained_validators = [AutoCompleteFieldValidator(model_object=model.CouponActive), validators.Int()]

class CouponActiveField(MyAutoCompleteField):
    """
        Autocomplete field for active coupon model object.
    """

    search_controller = "/Search/coupon_active"

    def __init__(self, not_empty=False, *args, **kw):
        kw['validator'] = CouponActiveFieldSchema()
        if not_empty:
            kw['validator'].chained_validators.append(validators.NotEmpty())
        super(MyAutoCompleteField, self).__init__(*args, **kw)

class TeacherField(DictionaryField):
    """
        Dictionary field for persons, who can teach.
    """

    params = ['records', 'empty_caption', 'caption_func', 'sort_func']
    def __init__(self, not_empty=False, *args, **kw):
        DataSelectField.__init__(self, *args, **kw)

        if self.records is None:
            self.records = model.Person.selectBy(canTeach=True)
        if getattr(self, 'label', None) is None:
            self.label = model.Person.model_title
        self.validator = validators.Int(not_empty=not_empty)

class TextWithHelperField(widgets.FormField):
    """A standard, single-line text field with drop-down helper."""

    template = """
    <span xmlns:py="http://purl.org/kid/ns#">

    <input type="text"
        name="${name}"
        class="${field_class}"
        id="${field_id}"
        value="${value}"
        py:attrs="attrs"
    />

    <select class="${field_class}"
            onchange="document.getElementById('${field_id}').value = this.value; this.value = ''"
    >
        <option py:for="value, desc in options"
            value="${value}"
            py:content="desc"
        />
    </select>

    </span>
    """

    params = ["attrs", "records"]
    params_doc = {'attrs': 'Dictionary containing extra (X)HTML attributes for the input tag'}
    attrs = {}

    def get_options(self, records):
        result = [('', '')]
        if len(records)<=0:
            return result
        if isinstance(records[0],tuple):
            result+=[(x[0], x[1]) for x in records]
        else:
            result += [(x, x) for x in records]
        return result

    def display(self, *args, **kw):
        records = kw.get('records', self.records)
        if callable(records):
            records = records()
        kw['options'] = self.get_options(records)
        return super(widgets.FormField, self).display(*args, **kw)

def column_parms(column):
    """
    Helper function to convert column attributes to
    parameters needed to initialize a widget.
    """
    d = Wrapper()

    not_empty = False
    if column.notNone:
        not_empty = (column.default == col.NoDefault)

    d.validator = dict(not_empty=not_empty)

    d.widget = dict(name=column.name, label=column.title)

    if column.default != col.NoDefault:
        d.widget['default'] = column.default
        d.validator['if_missing'] = column.default

    d.widget['validator'] = EmptyValidator(**d.validator)

    return d

@dispatch.generic()
def column_widget(column):
    pass

@column_widget.when("isinstance(column, SOGenderCol)")
def column_widget_gender_col(column):
    x = column_parms(column)
    x.widget['options'] = [
        ('male', _('Male')),
        ('female', _('Female')),
    ]
    x.widget['validator'] = GenderValidator(**x.validator)
    return widgets.SingleSelectField(**x.widget)

@column_widget.when("isinstance(column, col.SOIntCol) and column.name == 'session'")
def column_widget_session_col(column):
    x = column_parms(column)
    x.widget['options'] = [(i, str(i)) for i in xrange(1, 6)]  # TODO
    del x.widget['validator']
    return widgets.SingleSelectField(**x.widget)

@column_widget.when("isinstance(column, SOTariffCol)")
def column_widget_tariff_col(column):
    x = column_parms(column)
    del x.widget['validator']
    return TariffField(not_empty=x.validator['not_empty'], **x.widget)

@column_widget.when("isinstance(column, SOPhoneCol) and column.name == 'phone_main'")
def column_widget_phone_col(column):
    x = column_parms(column)
    return PhoneField( **x.widget)

@column_widget.when("isinstance(column, col.SOBLOBCol)")
def column_widget_blob_col(column):
    return widgets.FileField(name=column.name, label=column.title)

@column_widget.when("isinstance(column, col.SOKeyCol)")
def column_widget_fkey_col(column):
    x = column_parms(column)
    fkey_class = classregistry.findClass(column.foreignKey)

    return DictionaryField(
        not_empty=x.validator['not_empty'],
        model_object_name=column.foreignKey, **x.widget
    )

@column_widget.when("isinstance(column, col.SOKeyCol) and column.foreignKey == 'Plan'")
def column_widget_fkey_col(column):
    x = column_parms(column)
    fkey_class = classregistry.findClass(column.foreignKey)

    return DictionaryField(
        not_empty=x.validator['not_empty'],
        model_object_name=column.foreignKey,
        sort_func=[lambda x: x.sort(key=lambda y: (y.trend.title, -y.year))],
        **x.widget
    )

@column_widget.when("isinstance(column, col.SOKeyCol) and column.foreignKey == 'Topic'")
def column_widget_fkey_col(column):
    x = column_parms(column)
    return TopicField(
        not_empty=x.validator['not_empty'],
        **x.widget
    )

@column_widget.when("isinstance(column, col.SOKeyCol) and column.foreignKey == 'Student'")
def column_widget_fkey_col(column):
    x = column_parms(column)
    return StudentField(
        not_empty=x.validator['not_empty'],
        **x.widget
    )

@column_widget.when("isinstance(column, col.SOKeyCol) and column.foreignKey == 'StudentAgreement'")
def column_widget_fkey_col(column):
    x = column_parms(column)
    return AgreementField(
        not_empty=x.validator['not_empty'],
        **x.widget
    )

@column_widget.when("isinstance(column, col.SOKeyCol) and column.foreignKey == 'Customer'")
def column_widget_fkey_col(column):
    x = column_parms(column)
    return CustomerField(
        not_empty=x.validator['not_empty'],
        **x.widget
    )

@column_widget.when("isinstance(column, col.SOKeyCol) and column.foreignKey == 'Person'")
def column_widget_fkey_col(column):
    x = column_parms(column)
    return PersonField(
        not_empty=x.validator['not_empty'],
        **x.widget
    )

@column_widget.when("isinstance(column, col.SOCol)")
def column_widget_generic_col(column):
    x = column_parms(column)
    return widgets.TextField(**x.widget)

@column_widget.when("isinstance(column, col.SOIntCol)")
def column_widget_int_col(column):
    x = column_parms(column)
    x.widget['validator'] = validators.Int(**x.validator)
    return widgets.TextField(**x.widget)

@column_widget.when("isinstance(column, (col.SOFloatCol))")
def column_widget_float_col(column):
    x = column_parms(column)
    x.widget['validator'] = validators.Number(**x.validator)
    return widgets.TextField(**x.widget)

@column_widget.when("isinstance(column, (col.SOStringLikeCol))")
def column_widget_string_col(column):
    x = column_parms(column)
    if column.length:
        return widgets.TextField(**x.widget)
    else:
        return widgets.TextArea(rows=7, cols=50, **x.widget)

@column_widget.when("isinstance(column,col.SOEnumCol)")
def column_widget_enum_col(column):
    x = column_parms(column)
    x.widget['options'] = [(value, value) for value in column.enumValues ]
    x.widget['validator'] = validators.OneOf(column.enumValues)
    return widgets.SingleSelectField(**x.widget)

@column_widget.when("isinstance(column,col.SODateCol)")
def column_widget_date_col(column):
    x = column_parms(column)
    x.widget['not_empty'] = x.validator['not_empty']
    x.widget['format'] = '%d.%m.%Y'
    del x.widget['validator']
    return widgets.CalendarDatePicker(**x.widget)

@column_widget.when("isinstance(column,col.SODateTimeCol)")
def column_widget_datetime_col(column):
    x = column_parms(column)
    x.widget['not_empty'] = x.validator['not_empty']
    del x.widget['validator']
    return widgets.CalendarDateTimePicker(**x.widget)

@column_widget.when("isinstance(column,col.SOTimeCol)")
def column_widget_datetime_col(column):
    x = column_parms(column)
    x.widget['not_empty'] = x.validator['not_empty']
    del x.widget['validator']
    return TimeField(**x.widget)

@column_widget.when("isinstance(column, col.SOBoolCol)")
def column_boolean(column):
    x = column_parms(column)
    return widgets.CheckBox(**x.widget)

def get_records_from_table(cls, attr_name):
    records = [getattr(x, attr_name) for x in cls.select()]
    records.sort()

    return records

#@column_widget.when(
    #"isinstance(column, (col.SOStringLikeCol)) and "
    #"column.name.lower().find('educationtype') != -1")
#def column_education_type_string(column):
    #x = column_parms(column)
    #x.widget['records'] = lambda: get_records_from_table(model.EducationType, 'abbr')
    #return TextWithHelperField(**x.widget)

@column_widget.when("isinstance(column, (col.SOStringLikeCol)) and column.name == 'avgMark'")
def column_avg_mark(column):
    x = column_parms(column)
    x.widget['records'] = lambda: get_records_from_table(model.AvgMarkValues, 'value')
    return TextWithHelperField(**x.widget)

def column_avg_mark(column, name):
    x = column_parms(column)
    x.widget['records'] = lambda: get_records_from_table(model.AvgMarkValues, 'value')
    x.widget['name'] = name
    x.widget['attrs'] = {'size': 4}
    return TextWithHelperField(**x.widget)

@column_widget.when(
    "isinstance(column, (col.SOStringLikeCol)) and "
    "(column.name.startswith('possibleDay') or"
    " column.name.startswith('unpossibleDay'))")
def column_possible_day(column):
    x = column_parms(column)
    x.widget['records'] = lambda: get_records_from_table(model.PossibleDayValues, 'value')
    return TextWithHelperField(**x.widget)

def column_possible_day(name):
    x = Wrapper()
    x.widget = {
        'records': lambda: get_records_from_table(model.PossibleDayValues, 'value'),
        'name': name,
        'attrs': {'size': 4}
    }
    return TextWithHelperField(**x.widget)

def fields_for(sqlclass, fields=None):
    sqlclass_columns = so_columns(sqlclass)
    if fields:
        columnlist = fields
    else:
        columnlist = [FieldGroup(fields=sqlclass_columns.keys())]

    widgetlist = []

    for group in columnlist:
        for column in group.fields:
            widget = None
            if column in sqlclass_columns:
                widget = column_widget(sqlclass_columns[column])
            if column + 'ID' in sqlclass_columns:  # key column
                widget = column_widget(sqlclass_columns[column + 'ID'])

            if not widget:
                raise Exception, "Join or column %r not found in class %r" % \
                    (column, sqlclass.__name__)

            widgetlist.append(widget)

    return widgetlist

class FKeyItemViewWidget(widgets.Widget):
    """
        Create link to foreign key instance.
    """

    template = """
        <a href="/${obj.__class__.__name__}/${obj.id}/">
            ${getattr(obj, 'title', str(obj))}
        </a>
    """
fkey_item_view_widget = FKeyItemViewWidget()

@dispatch.generic()
def column_item_view(column, value):
    pass

@column_item_view.when("isinstance(column, SOGenderCol)")
def column_item_view_gender_col(column, value):
    if value is None:
        return _("*No value*")
    return (value == Gender.MALE) and _('Male') or _('Female')

@column_item_view.when("isinstance(column, col.SOBoolCol)")
def column_item_view_boolean_col(column, value):
    if value is None:
        return _("*No value*")
    return value and _('Yes') or _('No')

@column_item_view.when("isinstance(column, col.SOKeyCol)")
def column_item_view_fkey_col(column, value):
    fkey_class = classregistry.findClass(column.foreignKey)
    if value is None:
        return _("*No value*")
    obj = fkey_class.get(int(value))
    from genshi.template.plugin import ET
    return ET(fkey_item_view_widget.display(obj=obj))

@column_item_view.when("isinstance(column, col.SODateCol)")
def column_item_view_date_col(column, value):
    if value is None:
        return _("*No value*")
    return value.strftime("%d.%m.%Y")

@column_item_view.when("isinstance(column, col.SOTimeCol)")
def column_item_view_date_col(column, value):
    if value is None:
        return _("*No value*")
    return '%02d:%02d' % (value.hour, value.minute)

@column_item_view.when("column.name.lower().find('email') != -1")
def  column_item_view_email_col(column, value):
    escaped = cgi.escape(value)
    from genshi.input import XML
    return XML('<a href="mailto:%s">%s</a>' % (escaped, escaped))

@column_item_view.when(dispatch.strategy.default)
def column_item_view_generic_col(column, value):
    if value is None:
        return _("*No value*")
    return str(value)

class BackWidget(widgets.Widget):
    """
        Create back link from history.
    """

    params = ['method', 'dont_center']

    _("Back")

    template = """
        <div xmlns:py="http://purl.org/kid/ns#" id="back-widget"
            py:attrs="dont_center and {} or dict(align='center')">
            [ <a py:attrs="href=href">Back</a> ]
        </div>
    """

    # Correct values: back-button and referer
    method = 'back-button'
    dont_center = False

    def update_params(self, d):
        super(BackWidget, self).update_params(d)

        method = d['method']
        assert method in ['back-button', 'referer']

        if method == 'back-button':
            href = 'javascript:history.back()'
        else:
            assert method == 'referer'
            href = cherrypy.request.headerMap.get('Referer', 'javascript:history.back()')
        d['href'] = href

def add_back_widget(vars, back_widget=BackWidget()):
    vars['back_widget'] = back_widget
turbogears.view.variable_providers.append(add_back_widget)



class PhoneFieldValidator(validators.FancyValidator):
    """
    """
    def _to_python(self, value, state):
        if isinstance(value,dict):
            res = PhoneFormat.to_python(value)
        else:
            res = PhoneFormat.from_python(value)
        if res == PhoneFormat.bad_str():
            return PhoneFormat.bad_str(True)
        return res
    
    def _from_python(self, value, state):
        return self._to_python(value,state)

class PhoneSchema(Schema):
    """
        Validation schema which intakes Phone 
    """

    allow_extra_fields = False
    if_key_missing = None

    #chained_validators = [PhoneValidator(), PhoneFieldValidator()]
    chained_validators = [PhoneFieldValidator()]

class PhoneField(widgets.CompoundFormField):

    template = "mnemoply.templates.phone_main"
    
    
    def __init__(self, not_empty=False, *args, **kw):
        code = TextWithHelperField(name='code',
                                   label='Code',
                                   attrs ={'class':'textfield_small'},
                                   records = [(x.value,x.name ) for x in model.PhoneCode.select() ]
                                  )
        num  = widgets.TextField(name='num',
                                 attrs ={'class':'textfield_middle'},
                                 label='Number',
                                 help_text ='')
        btn = widgets.Button(name='Check',
                             label='',
                             default = _('Check'),
                             attrs=dict(onclick='remoteRequestForError (this.form,"phone_main","check_phone")'))
        kw['validator'] = PhoneSchema()
        super(PhoneField, self).__init__(fields=[code,num,btn], *args, **kw)

    def adjust_value(self, value, **kw):
        return self.validator.from_python(value)
class SyncDict(DictionaryField):
    
    def get_options(self, records):
        records = list(records)
        self.sort_records(records)
        captions = [(item.id, self.get_caption(item),dict(trend=item.trend.id, year=item.year))
            for item in records]
        result = [('', self.empty_caption,dict(trend=item.trend.id, year=item.year))]+captions
        return result


class PlanField(widgets.CompoundInputWidget):
    member_widgets = ['year', 'trend','plan', 'script']
    label ="Plan"
    name = "Plan"
    field_id = "plan_id"
    help_text = ""
    plan = SyncDict(name='plan', model_object_name='Plan',sort_func=[planSortFunction],label="Plan")
    year = YearField(name='year')
    trend = DictionaryField(name='trend', model_object_name='Trend',label="Trend")
    template = """
    <div>
        ${script()}
        ${year.display(value_for(year), **params_for(year))}
        ${trend.display(value_for(trend), **params_for(trend))}
        ${plan.display(value_for(plan), **params_for(plan))}
    </div>
    """
    def get_records(self):
        result = ""
        records =[  '{value: "%s", text:"%s", trend:"%s", year:"%s"}' %(rec.id, rec.title, rec.trend.id, rec.year) for rec in model.Plan.select()]
        result = "var lstFromServer = [%s];" %",\n".join(records)
        return ""
    def __init__(self, *args, **kw):
        widgets.CompoundInputWidget.__init__(self, *args, **kw)
        self.script = widgets.JSSource( self.get_records()+" function change() { selectChange('%s', ['trend','year'], 'plan') }" %self.name)
        self.trend.attrs = {'onchange':'change();'}
        self.year.attrs = {'onchange':'change();'}
        

class Mainmenu(widgets.Widget):
    
    params = ['mainmenu']
    mainmenu = []
    template = "mnemoply.templates.menu"
    
    def __init__(self,*args,**kw):
        widgets.Widget.__init__(self,name='mainmenu')
        m = menu.instance( list( model.auth.Menu.select(orderBy=['parent_id','ordinal'])))
        self.mainmenu = m.get_menu_with_permission()